#!/usr/bin/env python
#
# Copyright 2009, Digi International

import base64
import logging
import xml.etree.ElementTree as ET
from datetime import datetime


from google.appengine.api import urlfetch
from google.appengine.ext import db
from config import Config

SCI_VERSION = Config.SCI_VERSION
RCI_VERSION = Config.RCI_VERSION
DIA_TARGET = Config.DIA_TARGET

DEFAULT_IDIGI_URL = Config.DEFAULT_IDIGI_URL

class Sample(db.Model):
    channel_name = db.StringProperty(required=True)
    value = db.StringProperty(required=True)
    unit = db.StringProperty(required=False)
    timestamp = db.DateTimeProperty(required=False)


class iDigiClient:
  def __init__(self, username, password, req_url=DEFAULT_IDIGI_URL):
      self._req_url = req_url     
      self._username = username
      self._password = password
      self._auth = 'Basic ' + base64.encodestring('%s:%s' % (self._username,self._password))[:-1]
      self._basic_auth = "Basic " + base64.encodestring(
                            "%s:%s" % (username, password))
      self._basic_auth = self._basic_auth.replace('\n','')

  def _parse_to_do_command_data(self, sci_xml):
      """Returns contents of do_command subtree, raises Exception on error."""
      sci_tree = ET.fromstring(sci_xml)
      try:
          do_command = sci_tree.find('send_message/device/rci_reply/do_command')
          error = do_command.find('error')
      except Exception, e:
          logging.warning("error parsing SCI XML, contents: %s" % sci_xml)
          raise e
      if error is not None:
          desc = error.find('desc').text
          hint = error.find('hint').text
          raise Exception, "SCI Error: %s (%s)" % (desc, hint)
      
      data = do_command.find('data')
            
      return data
      
  def channel_get(self, device_id, channel_name, target=DIA_TARGET):
      print "get channel: %s" % channel_name
      root = ET.Element("sci_request")
      root.set("version", SCI_VERSION)
      send_message = ET.SubElement(root, "send_message")
      targets = ET.SubElement(send_message, "targets")
      device = ET.SubElement(targets, "device")
      device.set("id", device_id)
      rci_request = ET.SubElement(send_message, "rci_request")
      rci_request.set("version", RCI_VERSION)
      do_command = ET.SubElement(rci_request, "do_command")
      do_command.set("target", target)
      data = ET.SubElement(do_command, "data")
      channel_get = ET.SubElement(data, "channel_get")
      channel_get.set("name", channel_name)
      
      req_doc = ET.tostring(root)
      result = urlfetch.fetch(url=self._req_url,
                payload=req_doc,
                method=urlfetch.POST,
                deadline=10,
                headers={'Content-Type': 'application/x-www-form-urlencoded',
                         'Authorization': self._basic_auth})

      if result.status_code != 200:
          raise Exception, "iDigi Req Returned %s" % result.status_code
 
      data = self._parse_to_do_command_data(result.content)
      channel_get = data.find('channel_get')
      value = channel_get.get("value")
      if value is None:
          value = ''
      unit = channel_get.get("units")
      timestamp = None
      try:
          timestamp = datetime.strptime(channel_get.get("timestamp"),
                                        "%a %b %d %H:%M:%S %Y")
      except:
          pass       
      sample = Sample(
        channel_name=channel_name,
        value=value,
        unit=unit,
        timestamp=timestamp)
      
      return sample
          
      
  def channel_dump(self, device_id, target=DIA_TARGET):
      root = ET.Element("sci_request")
      root.set("version", SCI_VERSION)
      send_message = ET.SubElement(root, "send_message")
      targets = ET.SubElement(send_message, "targets")
      device = ET.SubElement(targets, "device")
      device.set("id", device_id)
      rci_request = ET.SubElement(send_message, "rci_request")
      rci_request.set("version", RCI_VERSION)
      do_command = ET.SubElement(rci_request, "do_command")
      do_command.set("target", target)
      data = ET.SubElement(do_command, "data")
      channel_get = ET.SubElement(data, "channel_dump")
      auth = 'Basic ' + base64.encodestring('%s:%s' % (self._username,self._password))[:-1]
      
      req_doc = ET.tostring(root)
      result = urlfetch.fetch(url=self._req_url,
                payload=req_doc,
                method=urlfetch.POST,
                deadline=10,
                headers={'Content-Type': 'application/x-www-form-urlencoded','Authorization': self._basic_auth})
      
      if result.status_code != 200:
          raise Exception, "iDigi Req auth %s;%s;%s;%s;%s;%s;%s" % (result.status_code, self._auth, self._basic_auth, device_id, auth, self._username, self._password)
      
      data = self._parse_to_do_command_data(result.content)
      channel_dump = data.find('channel_dump')
      
      samples = []
      for device in channel_dump:
          if device.tag != "device":
              continue
          device_name = device.get("name")
          for property in device:
              if property.tag != "channel":
                  continue
              channel_name = '.'.join( (device_name, property.get("name")) )
              value = property.get("value")
              if value == '':
                  value = '(n/a)'
              unit = property.get("units")  
              timestamp = None
              try:
                  timestamp = datetime.strptime(property.get("timestamp"),
                                                "%a %b %d %H:%M:%S %Y")
              except:
                  pass
              
              sample = Sample(
                channel_name=channel_name,
                value=value,
                unit=unit,
                timestamp=timestamp)
              samples.append(sample)

      return samples
  
  def channel_set(self, device_id, sample, target=DIA_TARGET):
      if not isinstance(sample, Sample):
          raise TypeError, "sample argument must be a Sample model object"
      
      root = ET.Element("sci_request")
      root.set("version", SCI_VERSION)
      send_message = ET.SubElement(root, "send_message")
      targets = ET.SubElement(send_message, "targets")
      device = ET.SubElement(targets, "device")
      device.set("id", device_id)
      rci_request = ET.SubElement(send_message, "rci_request")
      rci_request.set("version", RCI_VERSION)
      do_command = ET.SubElement(rci_request, "do_command")
      do_command.set("target", target)
      data = ET.SubElement(do_command, "data")
      channel_set = ET.SubElement(data, "channel_set")
      channel_set.set("name", sample.channel_name)
      channel_set.set("value", sample.value)
      if sample.unit is not None:
          channel_set.set("units", sample.unit)
      
      req_doc = ET.tostring(root)
      result = urlfetch.fetch(url=self._req_url,
                payload=req_doc,
                method=urlfetch.POST,
                deadline=10,
                headers={'Content-Type': 'application/x-www-form-urlencoded',
                     'Authorization': self._basic_auth})
    
      if result.status_code != 200:
          raise Exception, "iDigi Req Returned %s" % result.status_code
      
      return True
